{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 图像轮廓\n",
    "#### 基本理论\n",
    "轮廓可以简单地解释为连接所有连续点（沿着边界）的曲线，具有相同的颜色或强度。轮廓是形状分析和物体检测和识别的有用工具。\n",
    "\n",
    "- 处理轮廓时，使用的是二进制图像，首先应该使用阈值处理或者边缘检测( canny )\n",
    "- cv2.findContours(), cv2.drawContours()\n",
    "\n",
    "`findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> image, contours, hierarchy`\n",
    "- image: 源图像\n",
    "- mode: 轮廓检索模式\n",
    "    - cv2.RETR_EXTERNAL 只检测最外层轮廓\n",
    "    - cv2.RETR_LIST   提取所有轮廓，并放置在 list 中。建立的轮廓不建立等级关系\n",
    "    - cv2.RETR_CCOMP  提取所有轮廓，并将其组织为双层结构，顶层为连通域的外围边界，第二层为孔的内层边界，若孔内还有物体轮廓，则将其放在第一层\n",
    "    - cv2.RETR_TREE  提取所有轮廓，并重新建立网状的轮廓结构\n",
    "    - cv2.RETR_FLOODFILL\n",
    "- method: 轮廓的近似方法\n",
    "    - cv2.CHAIN_APPROX_NONE  获取每个轮廓的像素，相邻的两个点的像素位置差不超过1 \n",
    "    - cv2.CHAIN_APPROX_SIMPLE 压缩水平方向，垂直方向，对角线方向的元素，只保留该方向的终点坐标，如一个矩形需要四个坐标来保存轮廓信息\n",
    "    - cv2.CHAIN_APPROX_TC89_KCOS、cv2.CHAIN_APPROX_TC89_L1: 应用 Teh-Chin 链式近似算法的一个\n",
    "- contours: 检测到的轮廓保存的地方，每个轮廓保存为一个点向量\n",
    "- hierarchy: 包含图像的拓扑信息，图像的层次结构\n",
    "- offset:   ROI 图像时用到的参数\n",
    "[参数说明](https://docs.opencv.org/3.4/d3/dc0/group__imgproc__shape.html#gga819779b9857cc2f8601e6526a3a5bc71a48b9c2cb1056f775ae50bb68288b875e)\n",
    "    \n",
    "    \n",
    "`drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image`\n",
    "- image: \n",
    "- contours: 轮廓保存信息的参数，数据结构为列表\n",
    "- contourIdx: 轮廓绘制的标志量，若为负数，则代表绘制所有轮廓\n",
    "- color: 颜色\n",
    "- thickness: 轮廓线条粗细，默认值为 1，若为负值，则绘制在轮廓内部\n",
    "- lineType: 线条类型默认为 cv2.LINE_8 （8 连通线型）、cv2.LINE_4（4 连通线型）、cv2.LINE_AA （抗锯齿线型）\n",
    "- hierarchy: 层次结构信息\n",
    "- maxLevel: 绘制轮廓的最大等级默认 cv2.INTER_MAX\n",
    "- offset: 轮廓偏移参数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 例程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-27T14:16:43.642380Z",
     "start_time": "2019-03-27T14:16:39.855556Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "img = np.zeros((300, 300, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (200, 200), (255, 255, 255), -1)\n",
    "cv2.circle(img, (250, 250), 30, (255, 255, 255), -1)\n",
    "#img = cv2.imread(\"./sample_img/back.jpg\")\n",
    "imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "ret, thresh = cv2.threshold(imgray, 127, 255, 0) \n",
    "\n",
    "# findContours 只能处理二值图像\n",
    "contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "count = len(contours)\n",
    "for i in range(count):\n",
    "    if i % 2 == 0:\n",
    "        cv2.drawContours(img, contours, i, (0, 255, 0), 2, lineType=cv2.LINE_8, hierarchy=hierarchy)\n",
    "    else:\n",
    "        cv2.drawContours(img, contours, i, (0, 0, 255), 2, lineType=cv2.LINE_8, hierarchy=hierarchy)\n",
    "    \n",
    "cv2.imshow(\"img\", img)\n",
    "\n",
    "\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0vlmlcznmj208t09g3yg.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-27T14:17:14.859463Z",
     "start_time": "2019-03-27T14:17:13.050500Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "img = cv2.imread(\"./sample_img/contours.jpg\")\n",
    "imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "ret, thresh = cv2.threshold(imgray, 119, 255, 0)\n",
    "contours, hierarchy = cv2.findContours(\n",
    "    thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "\n",
    "img = cv2.drawContours(img, contours, -1, (0, 255, 0), 2,\n",
    "                       lineType=cv2.LINE_AA, hierarchy=hierarchy)\n",
    "\n",
    "\n",
    "cv2.imshow(\"img\", img)\n",
    "cv2.imshow(\"thresh\", thresh)\n",
    "\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0vm1d9hxtj20rj0lch5q.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-09T07:21:37.070219Z",
     "start_time": "2019-03-09T07:21:31.902152Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "def contours_demo(img):\n",
    "    \n",
    "    #dst = cv2.GaussianBlur(img, (3, 3), 0)\n",
    "    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "    ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)\n",
    "    cv2.imshow(\"binary image\", binary)\n",
    "\n",
    "    cloneImage, contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "    for i, contour in enumerate(contours):\n",
    "        cv2.drawContours(img, contours, i, (0, 0, 255), 2)\n",
    "        #print(i)\n",
    "    cv2.imshow(\"detect contours\", img)\n",
    "\n",
    "\n",
    "\n",
    "src = cv2.imread(\"./sample_img/circle.png\")\n",
    "cv2.imshow(\"src\", src)\n",
    "contours_demo(src)\n",
    "\n",
    "\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 凸包检测及其他轮廓特征"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0voy2sm32j207r08qt92.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`convexHull(points[, hull[, clockwise[, returnPoints]]]) -> hull`       \n",
    "- points: 输入的二维点集\n",
    "- hull: 返回的结果，即凸包\n",
    "- clockwise: 操作方向标识符 True：凸包为顺时针，False：逆时针。且设定 x 轴指向右， y 轴指向上方\n",
    "- returnPoints: 操作标识符，默认为 True：返回凸包的各个点，False：返回凸包各个点的指数"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 简易例程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-09T07:23:11.954633Z",
     "start_time": "2019-03-09T07:23:02.484076Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "def contours_demo(img):\n",
    "    \n",
    "    #dst = cv2.GaussianBlur(img, (3, 3), 0)\n",
    "    #gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "    ret, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)\n",
    "    #cv2.imshow(\"binary image\", binary)\n",
    "\n",
    "    cloneImage, contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "    #for i, contour in enumerate(contours):\n",
    "    #    cv2.drawContours(img, contours, i, (0, 0, 255), 2)\n",
    "        #print(i)\n",
    "    cv2.imshow(\"detect contours\", img)\n",
    "    \n",
    "    return contours\n",
    "    \n",
    "\n",
    "img = np.zeros((600, 600), dtype=np.uint8)\n",
    "for i in range(30):\n",
    "    x = random.randint(200, 250)\n",
    "    y = random.randint(200, 250)\n",
    "    #print(x, y)\n",
    "    cv2.circle(img, (x, y), 10, 255, -1)\n",
    "   \n",
    "contours = contours_demo(img)\n",
    "cnt = contours[0]\n",
    "hull = cv2.convexHull(cnt)\n",
    "hullcount = hull.shape[0]\n",
    "for i in range(hullcount-1):\n",
    "    x0 = hull[i][0][0]\n",
    "    y0 = hull[i][0][1]\n",
    "    x1 = hull[i+1][0][0]\n",
    "    y1 = hull[i+1][0][1]\n",
    "    cv2.line(img, (x0, y0), (x1, y1), 255, 2)\n",
    "    if i == hullcount-2:\n",
    "        #print(x1, y1)\n",
    "        x2 = hull[0][0][0]\n",
    "        y2 = hull[0][0][1]\n",
    "        cv2.line(img, (x1, y1), (x2, y2), 255, 2)    \n",
    "\n",
    "\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)    \n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0wkjou3twj20sr0er74f.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 综合例程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-09T08:10:38.807162Z",
     "start_time": "2019-03-09T08:10:20.761539Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "def convexHull_demo(img, sample, threshold):\n",
    "    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "    \n",
    "    cv2.imshow(\"gray\", gray)\n",
    "    blur = cv2.GaussianBlur(gray, (3, 3), 0)\n",
    "    ret, thresh = cv2.threshold(blur, threshold, 255, cv2.THRESH_BINARY)\n",
    "    cv2.imshow(\"thresh\", thresh)\n",
    "    cloneImage, contours, hierachy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    hull = []\n",
    "    for i in range(len(contours)-1):\n",
    "        hull.append(cv2.convexHull(contours[i], True))\n",
    "\n",
    "\n",
    "    for i in range(len(contours)-1): # 减 1 的作用是去掉图像最外层的轮廓，但是有些图像没有\n",
    "        cv2.drawContours(sample, contours, i, (255, 0, 0), 2, cv2.LINE_8, hierachy)\n",
    "        cv2.drawContours(sample, hull, i, (0, 255, 0), 2, cv2.LINE_8)\n",
    "        \n",
    "    cv2.imshow(\"sample\", sample)  \n",
    "    \n",
    "    \n",
    "def nothing(x):\n",
    "    pass\n",
    "        \n",
    "cv2.namedWindow(\"sample\")        \n",
    "cv2.createTrackbar(\"threshold\", \"sample\", 0, 255, nothing)\n",
    "\n",
    "\n",
    "img = cv2.imread(\"./sample_img/contours_2.jpg\", 1)   \n",
    "\n",
    "\n",
    "\n",
    "while(1):\n",
    "    sample = np.ones_like(img)\n",
    "    threshold = cv2.getTrackbarPos(\"threshold\", \"sample\")\n",
    "    #print(threshold)\n",
    "    convexHull_demo(img, sample, threshold)\n",
    "    k = cv2.waitKey(1) & 0xff\n",
    "    if k == 27:\n",
    "        break\n",
    "\n",
    "\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0wlw0egy0j20wd0hxdkl.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 轮廓近似\n",
    "`approxPolyDP(curve, epsilon, closed[, approxCurve]) -> approxCurve`  以指定的精度近似多边形曲线\n",
    "- curve 二维矩阵\n",
    "- epsilon 指定近似精度的参数   epsilon = rate*cv2.arcLength(contours[i], True)  # rate 为趋近的比例 如取 0.1， 0.01\n",
    "- closed 如果为真，则近似值曲线是闭合的(它的第一个和最后一个顶点是闭合的)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T06:50:46.519258Z",
     "start_time": "2019-03-11T06:50:41.238275Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (200, 200), (400, 400), 255, -1)\n",
    "cv2.rectangle(img, (250, 200), (260, 220), 0, -1)\n",
    "cv2.rectangle(img, (200, 300), (220, 320), 0, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "rate = 0.01 # 不同的取值会有不同的结果\n",
    "for i in range(count):\n",
    "    epsilon = rate*cv2.arcLength(contours[i], True) \n",
    "    approx = cv2.approxPolyDP(contours[i], epsilon, True)\n",
    "    cv2.drawContours(img, approx, -1, (0, 0, 255), 10)\n",
    "\n",
    "for i in range(len(approx)-1):\n",
    "    pt1 = (approx[i][0][0], approx[i][0][1])\n",
    "    pt2 = (approx[i+1][0][0], approx[i+1][0][1])\n",
    "    cv2.line(img, pt1, pt2, (0, 255, 0), 1)\n",
    "    \n",
    "    t = 1\n",
    "    if t == 1:\n",
    "        cv2.line(img, (approx[0][0][0], approx[0][0][1]), (approx[len(approx)-1][0][0], approx[len(approx)-1][0][1]), (0, 255, 0), 1)\n",
    "        t = t + 1\n",
    "        \n",
    "\n",
    "\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst     \"+str(rate), img)    \n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T06:50:00.046775Z",
     "start_time": "2019-03-11T06:49:59.848893Z"
    }
   },
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yutatcldj20ed0f0wek.jpg)\n",
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yutlohryj20ed0f0jrh.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 矩形边框"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-28T04:23:53.417120Z",
     "start_time": "2019-03-28T04:23:51.347309Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (200, 200), 255, -1)\n",
    "cv2.rectangle(img, (200, 200), (300, 300), 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "for i in range(count):\n",
    "    x, y, w, h = cv2.boundingRect(contours[i])\n",
    "    img = cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yv683lo4j20ed0f0aa3.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 旋转矩形"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 141,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T11:36:53.324836Z",
     "start_time": "2019-03-11T11:36:48.558556Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "((300.0, 300.0), (98.99495697021484, 98.99495697021484), -45.0)\n",
      "((150.0, 125.0), (50.0, 100.0), -90.0)\n"
     ]
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (200, 150), 255, -1)\n",
    "#cv2.rectangle(img, (200, 200), (300, 300), 255, -1)\n",
    "cv2.circle(img, (300, 300), 50, 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "for i in range(count):\n",
    "    rect = cv2.minAreaRect(contours[i]) # 返回 ((center_x, center_y), h, w, degree)\n",
    "    print(rect)\n",
    "    box = cv2.boxPoints(rect)\n",
    "    box = np.int0(box)\n",
    "    img = cv2.drawContours(img,[box],0,(0,0,255),2)\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yvlxra1zj20ed0f0jrh.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 最小封闭圆"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T07:19:20.084524Z",
     "start_time": "2019-03-11T07:19:15.693030Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (200, 200), 255, -1)\n",
    "cv2.rectangle(img, (211, 211), (300, 300), 255, -1)\n",
    "#cv2.circle(img, (300, 300), 50, 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "\n",
    "for i in range(count):\n",
    "    (x, y), radius = cv2.minEnclosingCircle(contours[i])\n",
    "    center = (int(x), int(y))\n",
    "    radius = int(radius)\n",
    "    img = cv2.circle(img, center, radius, (0, 255, 0), 2)\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yvobwh8bj20ed0f0aa6.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 拟合椭圆 （至少需要 5 个点）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T07:21:44.463144Z",
     "start_time": "2019-03-11T07:21:40.106631Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (200, 200), 255, -1)\n",
    "cv2.rectangle(img, (201, 201), (300, 300), 255, -1)\n",
    "#cv2.circle(img, (300, 300), 50, 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "\n",
    "for i in range(count):\n",
    "    ellipse = cv2.fitEllipse(contours[i])\n",
    "    img = cv2.ellipse(img,ellipse,(0,255,0),2)\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 拟合直线"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T07:23:23.378704Z",
     "start_time": "2019-03-11T07:23:18.532470Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (200, 200), 255, -1)\n",
    "cv2.rectangle(img, (201, 201), (300, 300), 255, -1)\n",
    "cv2.circle(img, (300, 300), 50, 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(\n",
    "    binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "\n",
    "for i in range(count):\n",
    "    rows, cols = img.shape[:2]\n",
    "    [vx, vy, x, y] = cv2.fitLine(contours[i], cv2.DIST_L2, 0, 0.01, 0.01)\n",
    "    lefty = int((-x*vy/vx) + y)\n",
    "    righty = int(((cols-x)*vy/vx)+y)\n",
    "    img = cv2.line(img, (cols-1, righty), (0, lefty), (0, 255, 0), 2)\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yvsg090lj20ed0f00su.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 轮廓属性"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "纵横比（Aspect Ratio） ![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yvv0wz1oj2055016742.jpg)\n",
    "\n",
    "extent ：是物体面积与边界矩形面积的比值 ![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yw1tyghtj20830160sj.jpg)\n",
    "\n",
    "Solidity ：坚实度是等高线面积与其凸包面积之比。![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yw478tnyj206n011mwy.jpg)\n",
    "\n",
    "Equivalent Diameter ：等效直径是面积与等值线面积相等的圆的直径。 ![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0yw5sgmnkj20a10190sj.jpg)\n",
    "\n",
    "Orientation 方向是物体所指向的角度\n",
    "\n",
    "最大最小值及其位置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T07:54:11.702754Z",
     "start_time": "2019-03-11T07:54:06.217887Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "横纵比： 1.0\n",
      "extent 0.715341204425633\n",
      "solidity 0.838911465892598\n",
      "equi_diameter 191.826117780188\n",
      "(217.58111572265625, 178.86651611328125) (220.607177734375, 291.5225524902344)\n",
      "[[100 100]\n",
      " [100 101]\n",
      " [100 102]\n",
      " ...\n",
      " [300 298]\n",
      " [300 299]\n",
      " [300 300]]\n",
      "29.0 30.0 (211, 201) (100, 100)\n",
      "(255.0, 1.3856864953414558, 0.0, 0.0)\n"
     ]
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (300, 200), (255, 2, 0), -1)\n",
    "cv2.rectangle(img, (211, 201), (300, 300), 255, -1)\n",
    "#cv2.circle(img, (300, 300), 50, 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(\n",
    "    binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "\n",
    "cnt = contours[0]\n",
    "x, y, w, h = cv2.boundingRect(cnt)\n",
    "aspect_ratio = float(w)/h\n",
    "print(\"横纵比：\", aspect_ratio)\n",
    "\n",
    "\n",
    "area = cv2.contourArea(cnt)\n",
    "x, y, w, h = cv2.boundingRect(cnt)\n",
    "rect_area = w*h\n",
    "extent = float(area)/rect_area\n",
    "print(\"extent\", extent)\n",
    "\n",
    "area = cv2.contourArea(cnt)\n",
    "hull = cv2.convexHull(cnt)\n",
    "hull_area = cv2.contourArea(hull)\n",
    "solidity = float(area)/hull_area\n",
    "print(\"solidity\", solidity)\n",
    "\n",
    "area = cv2.contourArea(cnt)\n",
    "equi_diameter = np.sqrt(4*area/np.pi)\n",
    "print(\"equi_diameter\", equi_diameter)\n",
    "\n",
    "(x, y), (MA, ma), angle = cv2.fitEllipse(cnt)  # 返回的是椭圆的外切矩形\n",
    "print((x, y), (MA, ma))\n",
    "\n",
    "mask = np.zeros(gray.shape, np.uint8)\n",
    "cv2.drawContours(mask, [cnt], 0, 255, -1)\n",
    "pixelpoints = np.transpose(np.nonzero(mask))\n",
    "print(pixelpoints)\n",
    "#pixelpoints = cv2.findNonZero(mask) # Numpy 里面的方法\n",
    "\n",
    "min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray,mask = mask)\n",
    "print(min_val, max_val, min_loc, max_loc)\n",
    "\n",
    "\n",
    "mean_val = cv2.mean(img,mask = mask)\n",
    "print(mean_val)\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "极端点 ![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0ywc4hgqsj20ci0d4ab3.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T07:42:05.359589Z",
     "start_time": "2019-03-11T07:42:02.060472Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import random\n",
    "\n",
    "\n",
    "img = np.zeros((500, 500, 3), dtype=np.uint8)\n",
    "cv2.rectangle(img, (100, 100), (300, 200), 255, -1)\n",
    "cv2.rectangle(img, (211, 201), (300, 300), 255, -1)\n",
    "cv2.circle(img, (300, 300), 50, 255, -1)\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)\n",
    "_, contours, hieriachy = cv2.findContours(\n",
    "    binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n",
    "count = len(contours)\n",
    "\n",
    "cnt = contours[0]\n",
    "leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])\n",
    "rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])\n",
    "topmost = tuple(cnt[cnt[:, :, 1].argmin()][0])\n",
    "bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0])\n",
    "\n",
    "cv2.line(img, leftmost, (leftmost[0]+1, leftmost[1]+1), (0, 255, 0), 5)\n",
    "cv2.line(img, rightmost, (rightmost[0]+1, rightmost[1]+1), (0, 255, 0), 5)\n",
    "cv2.line(img, topmost, (topmost[0]+1, topmost[1]+1), (0, 255, 0), 5)\n",
    "cv2.line(img, bottommost, (bottommost[0]+1, bottommost[1]+1), (0, 255, 0), 5)\n",
    "\n",
    "\n",
    "cv2.imshow(\"dst\", img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-11T07:39:40.025514Z",
     "start_time": "2019-03-11T07:39:40.006525Z"
    }
   },
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39gy1g0ywbwaqifj20ed0f00st.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 图像的矩\n",
    "图像矩的应用广泛，包括模式识别、目标分类、目标识别、方位估计、图像编码与重构。\n",
    "\n",
    "图像矩描述的是图像形状的全局特征，包括图像中不同类型的几何特性。如大小、位置、方向、形状等。\n",
    "\n",
    "一阶矩与形状有关、二阶矩显示曲线围绕直线平均值的扩展程度，三阶矩是关于平均值的对称性的测量。二阶矩和三阶矩可以导出共 7 个不变矩，而不变矩是图像的同居特性，满足平移、伸缩、旋转均不变的不变性，应用广泛。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`moments(array[, binaryImage]) -> retval` 计算图像所有的矩（最高为三阶矩）\n",
    "- array: 二维数组 \n",
    "- binaryImage: bool 类型，默认 False，若为 True 则所有非 0 像素为 1\n",
    "\n",
    "\n",
    "`contourArea(contour[, oriented]) -> retval` 计算轮廓面积\n",
    "- contour: 轮廓点（二维）\n",
    "- oriented: bool 类型，面向区域标识符。为 True 返回一个带符号的面积值，正负取决于轮廓的方向，可以根据该标识符确定轮廓的位置。默认参数为 False 返回绝对值\n",
    "\n",
    "\n",
    "`arcLength(curve, closed) -> retval` 计算轮廓长度（轮廓周长，曲线长度）\n",
    "- curve:  二维点集\n",
    "- closed: bool 类型，指示曲线是否封闭的标识符。默认封闭"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 简易例程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-09T09:40:10.566422Z",
     "start_time": "2019-03-09T09:40:09.341147Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 m00: 204.0 opencv area: 204.0 length: 208.0\n",
      "1 m00: 202.0 opencv area: 202.0 length: 205.65685415267944\n",
      "2 m00: 7866.0 opencv area: 7866.0 length: 332.04877281188965\n",
      "3 m00: 7820.0 opencv area: 7820.0 length: 329.70562267303467\n"
     ]
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "# 创建一个特殊的案例来说明\n",
    "img = np.ones((500, 500, 3), dtype=np.uint8)*255\n",
    "cv2.circle(img, (200, 200), 50, (0, 0, 0), -1)\n",
    "cv2.line(img, (300, 300), (300, 400), (0, 0, 0), 1)\n",
    "\n",
    "# 正常图片测试\n",
    "#img = cv2.imread(\"./sample_img/Moments.jpg\")\n",
    "\n",
    "\n",
    "\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) \n",
    "blur = cv2.GaussianBlur(gray, (3, 3), 0)\n",
    "canny = cv2.Canny(blur, 0, 100, 3)\n",
    "_, contours, hierarchy = cv2.findContours(\n",
    "    canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "count = len(contours)\n",
    "moment = []  # 计算矩\n",
    "for i in range(count):\n",
    "    moment.append(cv2.moments(contours[i], False))\n",
    "\n",
    "mc = [] # 质心\n",
    "area = [] # 面积\n",
    "length = [] # 长度\n",
    "for i in range(count):  # 计算中心矩（质心）\n",
    "    if moment[i]['m00'] != 0:\n",
    "        cx = int(moment[i]['m10']/moment[i]['m00'])\n",
    "        cy = int(moment[i]['m01']/moment[i]['m00'])\n",
    "    else:\n",
    "        cx = cy = 9999\n",
    "    mc.append([cx, cy])\n",
    "    area.append(cv2.contourArea(contours[i]))\n",
    "    length.append(cv2.arcLength(contours[i], True))\n",
    "    print(str(i), \"m00:\", moment[i][\"m00\"],\n",
    "          \"opencv area:\", area[i], \"length:\", length[i])\n",
    "\n",
    "\n",
    "sample = np.zeros_like(img)\n",
    "for i in range(count):\n",
    "    cv2.drawContours(sample, contours, i, (0, 255, 0), 2, 8, hierarchy)\n",
    "    cv2.circle(sample, (int(mc[i][0]), int(mc[i][1])), 4, (255, 0, 0))\n",
    "\n",
    "cv2.imshow(\"src\", img)\n",
    "cv2.imshow(\"sample\", sample)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0wog3klk3j20ro0etgls.jpg)\n",
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0wohv074wj20s60lcnfl.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 带阈值调节例程\n",
    "以下程序适合小图片处理，大图片会耗费大量内存，特别是在绘制轮廓的时候"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-09T12:00:56.633843Z",
     "start_time": "2019-03-09T12:00:47.115321Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import time\n",
    "\n",
    "def moment_demo(img, sample, threshold):\n",
    "    \n",
    "\n",
    "    canny = cv2.Canny(img, threshold, threshold*2, 3)\n",
    "    _, contours, hierarchy = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n",
    "\n",
    "    count = len(contours)\n",
    "    #moment = []  # 计算矩\n",
    "    area = []\n",
    "    length = []\n",
    "    moment = [cv2.moments(contours[i], False) for i in range(count)]\n",
    "    # cx, cy 为质心计算公式\n",
    "    cx = [int(moment[i]['m10']/moment[i]['m00']) if (moment[i]['m00'] != 0) else 9999 for i in range(count)]\n",
    "    cy = [int(moment[i]['m01']/moment[i]['m00']) if (moment[i]['m00'] != 0) else 9999 for i in range(count)]\n",
    "    \n",
    "    area = [cv2.contourArea(contours[i]) for i in range(count)]\n",
    "    length = [cv2.arcLength(contours[i], True) for i in range(count)]                                \n",
    "    for i in range(count): # 这里会耗费很大的内存\n",
    "        cv2.drawContours(sample, contours, i, (0, 255, 0), 2, 8, hierarchy) # 绘制轮廓\n",
    "        cv2.circle(sample, (int(cx[i]), int(cy[i])), 4, (255, 0, 0)) # 绘制质心\n",
    "        #print(str(i), \"m00:\", moment[i][\"m00\"],\"opencv area:\", area[i], \"length:\", length[i])\n",
    "        \n",
    "    \n",
    "\n",
    "\n",
    "def nothing(x):\n",
    "    pass\n",
    "\n",
    "\n",
    "cv2.namedWindow(\"sample\")\n",
    "cv2.createTrackbar(\"threshold\", \"sample\", 1, 255, nothing)\n",
    "img = cv2.imread(\"./sample_img/ml.png\")\n",
    "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "blur = cv2.GaussianBlur(gray, (3, 3), 0)\n",
    "\n",
    "\n",
    "while(1):\n",
    "    t0 = time.time()\n",
    "    sample = np.zeros_like(img)\n",
    "    threshold = cv2.getTrackbarPos(\"threshold\", \"sample\")\n",
    "    moment_demo(blur, sample,threshold)\n",
    "    #print(time.time()-t0)\n",
    "    \n",
    "    cv2.imshow(\"sample\", sample)\n",
    "    if (cv2.waitKey(1) & 0xff) == 27:\n",
    "        break\n",
    "    \n",
    "\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0wsko5at8j207d08fq2x.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 分水岭算法\n",
    "灰度图像的像素值当作海拔的高度，在极小值处打一个小孔，将水注入，得到一个分水岭\n",
    "\n",
    "`watershed(image, markers) -> markers`\n",
    "- image:\n",
    "- makers: "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 例程\n",
    "使用分水岭算法和距离变换分割相互接触的物体"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-10T11:22:56.484393Z",
     "start_time": "2019-03-10T11:22:53.953852Z"
    }
   },
   "outputs": [],
   "source": [
    "# %matplotlib notebook\n",
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def opening_demo(img):\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))\n",
    "    opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)\n",
    "    # opening = cv2.morphologyEx(img, 2, kernel) # 同上\n",
    "    cv2.imshow(\"opening\", opening)\n",
    "    return opening\n",
    "\n",
    "\n",
    "def closing_demo(img):\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))\n",
    "    closing = cv2.morphologyEx(img, 3, kernel)\n",
    "    cv2.imshow(\"closing\", closing)\n",
    "    return closing\n",
    "\n",
    "\n",
    "def dilation_demo(img):\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))\n",
    "    dilation = cv2.dilate(img, kernel, iterations=2)\n",
    "    cv2.imshow(\"dilation\", dilation)\n",
    "    return dilation\n",
    "\n",
    "\n",
    "def distanceTransform_demo(gray):\n",
    "    \"\"\"\n",
    "    src: 8 位单通道\n",
    "    \"\"\"\n",
    "    dis_transform = cv2.distanceTransform(src=gray,distanceType=cv2.DIST_L2,maskSize=5)\n",
    "    scale = cv2.convertScaleAbs(dis_transform)\n",
    "    normalize = cv2.normalize(dis_transform, None, 255,0, cv2.NORM_MINMAX, cv2.CV_8UC1)\n",
    "    cv2.imshow(\"dis_transform\", dis_transform)\n",
    "    #cv2.imshow(\"scale\", scale)\n",
    "    cv2.imshow(\"normalize\", normalize)\n",
    "    return normalize\n",
    "\n",
    "\n",
    "def watershed_demo(img):\n",
    "    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "    ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)  # 看看二值化后的效果\n",
    "    cv2.imshow(\"thresh\", thresh)\n",
    "    \n",
    "    # 用开运算去除白点噪声\n",
    "    opening = opening_demo(thresh)\n",
    "    \n",
    "    # 用闭运算去除物体上的孔洞\n",
    "    closing = closing_demo(opening)\n",
    "    \n",
    "    # 膨胀物体的边界，可以确保物体的区域被保留，包含了前景物和一部分背景\n",
    "    sure_bg = dilation_demo(closing)\n",
    "    \n",
    "    # 距离变换\n",
    "    dist_transform = distanceTransform_demo(opening)\n",
    "    \n",
    "    # 确定前景物，大概率能确定为前景物的形状\n",
    "    ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)\n",
    "    cv2.imshow(\"sure_fg\", sure_fg)\n",
    "\n",
    "    sure_fg = np.uint8(sure_fg)\n",
    "\n",
    "    # 获得一块区域，该区域是前景物与背景交界的一部分\n",
    "    unknown = cv2.subtract(sure_bg, sure_fg)\n",
    "    cv2.imshow(\"unknown\", unknown)\n",
    "\n",
    "    # Marker labelling 创建一个模板用来标记不同的区域，把连通的区域绘制不同的标签\n",
    "    # 未知区域标记为 0，其他区域标记为不同的整数\n",
    "    _, markers = cv2.connectedComponents(sure_fg)   \n",
    "    \n",
    "    # 使模板加 1 ，确保背景不是 0\n",
    "    markers = markers + 1\n",
    "    # 使背景标记为 0\n",
    "    markers[unknown==255] = 0\n",
    "    \n",
    "    plt.imshow(markers, cmap='jet') # 画出 jet 形式的图，可以清晰的看出每个硬币的位置\n",
    "    plt.title(\"markers jet\")\n",
    "    plt.xticks([]), plt.yticks([])\n",
    "    # 用 OpenCV 的方式显示 colormap，但是色彩没有 plt 画出来的色彩丰富\n",
    "    colormap = cv2.applyColorMap(src=np.uint8(markers), colormap=cv2.COLORMAP_JET)\n",
    "    cv2.imshow(\"colormap\", colormap)\n",
    "    \n",
    "    \n",
    "    # 分水岭算法，得到的边界标记为 -1\n",
    "    markers = cv2.watershed(img, markers)\n",
    "    cv2.imshow(\"marker\", markers)\n",
    "    img[markers == -1] = [0,0,255] # 若为 -1 代表是边界\n",
    "    cv2.imshow(\"dst\", img)\n",
    "    \n",
    "\n",
    "def main():\n",
    "    img = cv2.imread('./sample_img/water_coins.jpg')\n",
    "    #img = cv2.resize(img, (1366, 768))\n",
    "    watershed_demo(img)\n",
    "\n",
    "\n",
    "main()\n",
    "cv2.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除，0表示只用键输入结束窗口\n",
    "cv2.destroyAllWindows()  # 关闭所有窗口"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-10T11:22:36.822697Z",
     "start_time": "2019-03-10T11:22:34.497037Z"
    }
   },
   "outputs": [],
   "source": [
    "# %matplotlib notebook\n",
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "def opening_demo(img):\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))\n",
    "    opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)\n",
    "    # opening = cv2.morphologyEx(img, 2, kernel) # 同上\n",
    "    cv2.imshow(\"opening\", opening)\n",
    "    return opening\n",
    "\n",
    "\n",
    "def closing_demo(img):\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))\n",
    "    closing = cv2.morphologyEx(img, 3, kernel)\n",
    "    cv2.imshow(\"closing\", closing)\n",
    "    return closing\n",
    "\n",
    "\n",
    "def dilation_demo(img):\n",
    "    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))\n",
    "    dilation = cv2.dilate(img, kernel, iterations=2)\n",
    "    cv2.imshow(\"dilation\", dilation)\n",
    "    return dilation\n",
    "\n",
    "\n",
    "def watershed_demo(img):\n",
    "    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n",
    "    ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)  # 看看二值化后的效果\n",
    "    cv2.imshow(\"thresh\", thresh)\n",
    "    \n",
    "    # 用开运算去除白点噪声\n",
    "    opening = opening_demo(thresh)\n",
    "    \n",
    "    # 用闭运算去除物体上的孔洞\n",
    "    closing = closing_demo(opening)\n",
    "    \n",
    "    # 膨胀物体的边界，可以确保物体的区域不饿保留\n",
    "    sure_bg = dilation_demo(closing)\n",
    "    \n",
    "    \n",
    "    dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)\n",
    "    cv2.imshow(\"dist_transform\", dist_transform)\n",
    "    \n",
    "    ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)\n",
    "    cv2.imshow(\"sure_fg\", sure_fg)\n",
    "    \n",
    "    sure_fg = np.uint8(sure_fg)\n",
    "    unknown = cv2.subtract(sure_bg, sure_fg)\n",
    "    cv2.imshow(\"unknown\", unknown)\n",
    "\n",
    "    # Marker labelling 创建一个模板用来标记不同的区域\n",
    "    # 未知区域标记为 0，其他区域标记为不同的整数\n",
    "    ret, markers = cv2.connectedComponents(sure_fg)   \n",
    "    \n",
    "    # 使模板加 1 ，确保背景不是 0\n",
    "    markers = markers + 1\n",
    "    # 使背景标记为 0\n",
    "    markers[unknown==255] = 0\n",
    "    \n",
    "    plt.imshow(markers, cmap='jet') # 画出 jet 形式的图，可以清晰的看出每个硬币的位置\n",
    "    plt.xticks([]), plt.yticks([])\n",
    "    \n",
    "\n",
    "    markers = cv2.watershed(img, markers)\n",
    "    img[markers == -1] = [0,0,255]\n",
    "    cv2.imshow(\"marker\", img)\n",
    "    \n",
    "\n",
    "def main():\n",
    "    img = cv2.imread('./sample_img/water_coins.jpg')\n",
    "    #img = cv2.resize(img, (1366, 768))\n",
    "    watershed_demo(img)\n",
    "\n",
    "\n",
    "main()\n",
    "cv2.waitKey(0)  # 等有键输入或者1000ms后自动将窗口消除，0表示只用键输入结束窗口\n",
    "cv2.destroyAllWindows()  # 关闭所有窗口"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0xr7z7gojj211109i0x1.jpg)\n",
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0xr9eyofdj20960bcweo.jpg)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-10T08:02:32.787645Z",
     "start_time": "2019-03-10T08:02:32.782648Z"
    }
   },
   "source": [
    "#### 更多资料\n",
    "[分水岭分割介绍](http://www.cmm.mines-paristech.fr/~beucher/wtshed.html)\n",
    "[基于距离变换和分水岭算法的图像分割](https://docs.opencv.org/3.4/d2/dbd/tutorial_distance_transform.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 图像修复\n",
    "`inpaint(src, inpaintMask, inpaintRadius, flags[, dst]) -> dst`\n",
    "- src: 源图像 8 位单通道或三通道图像\n",
    "- inpaintMask:修复掩模，为 8 位单通道图像。非 0 像素表示要修复的图像\n",
    "- inpaintRadius:double 需要修补的每个点的圆形邻域，为修复算法的参考半径\n",
    "- flags:修复方法标识符\n",
    "    - cv2.INPAINT_NS   基于 Navier-Stokes 方程\n",
    "    - cv2.INPAINT_TELEA   Alexandru Telea "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-03-10T06:48:28.050183Z",
     "start_time": "2019-03-10T06:47:51.235341Z"
    }
   },
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "drawing = False # 鼠标按下为 True\n",
    "ix, iy = -1, -1\n",
    "\n",
    "\n",
    "def on_mouse(event, x, y, flags, param):\n",
    "    global ix, iy, drawing\n",
    "\n",
    "    if event == cv2.EVENT_LBUTTONDOWN: # 鼠标放下\n",
    "        drawing = True\n",
    "        ix, iy = x, y # 鼠标放下时的坐标\n",
    "\n",
    "    elif event == cv2.EVENT_MOUSEMOVE: # 鼠标移动\n",
    "        if drawing == True:\n",
    "                cv2.line(img, (x, y), (x, y), (255, 255, 255), 5)\n",
    "                cv2.line(inpaintmask, (x, y), (x, y), 255, 5)\n",
    "\n",
    "    elif event == cv2.EVENT_LBUTTONUP: # 鼠标提起，结束绘制\n",
    "        drawing = False\n",
    "        \n",
    "\n",
    "img = cv2.imread(\"./sample_img/inpaint.jpg\")\n",
    "cv2.namedWindow(\"image\")\n",
    "cv2.setMouseCallback(\"image\", on_mouse) # 功能绑定\n",
    "inpaintmask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)\n",
    "\n",
    "while(1):\n",
    "    cv2.imshow(\"image\", img)\n",
    "    cv2.imshow(\"inpaintmask\", inpaintmask)\n",
    "    k = cv2.waitKey(1) & 0xFF\n",
    "    if k == ord('c'):\n",
    "        cv2.imwrite(\"./sample_img/inpaint_2.jpg\", img)\n",
    "    if k == ord('f'):\n",
    "        fix_img = cv2.inpaint(img, inpaintmask, 2, cv2.INPAINT_TELEA)\n",
    "        cv2.imshow(\"fix_img\", fix_img)\n",
    "    \n",
    "    if k == 27:\n",
    "        break\n",
    "        \n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](https://ws1.sinaimg.cn/large/acbcfa39ly1g0wu4984eij211k0lctu2.jpg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "303.542px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
